// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/client/vertex_array_object_manager.h"

#include <stddef.h>
#include <stdint.h>

#include "base/logging.h"
#include "base/macros.h"
#include "gpu/command_buffer/client/gles2_cmd_helper.h"
#include "gpu/command_buffer/client/gles2_implementation.h"

namespace gpu {
namespace gles2 {

    static GLsizei RoundUpToMultipleOf4(GLsizei size)
    {
        return (size + 3) & ~3;
    }

    // A 32-bit and 64-bit compatible way of converting a pointer to a GLuint.
    static GLuint ToGLuint(const void* ptr)
    {
        return static_cast<GLuint>(reinterpret_cast<size_t>(ptr));
    }

    // This class tracks VertexAttribPointers and helps emulate client side buffers.
    //
    // The way client side buffers work is we shadow all the Vertex Attribs so we
    // know which ones are pointing to client side buffers.
    //
    // At Draw time, for any attribs pointing to client side buffers we copy them
    // to a special VBO and reset the actual vertex attrib pointers to point to this
    // VBO.
    //
    // This also means we have to catch calls to query those values so that when
    // an attrib is a client side buffer we pass the info back the user expects.

    class GLES2_IMPL_EXPORT VertexArrayObject {
    public:
        // Info about Vertex Attributes. This is used to track what the user currently
        // has bound on each Vertex Attribute so we can simulate client side buffers
        // at glDrawXXX time.
        class VertexAttrib {
        public:
            VertexAttrib()
                : enabled_(false)
                , buffer_id_(0)
                , size_(4)
                , type_(GL_FLOAT)
                , normalized_(GL_FALSE)
                , pointer_(NULL)
                , gl_stride_(0)
                , divisor_(0)
                , integer_(GL_FALSE)
            {
            }

            bool enabled() const
            {
                return enabled_;
            }

            void set_enabled(bool enabled)
            {
                enabled_ = enabled;
            }

            GLuint buffer_id() const
            {
                return buffer_id_;
            }

            void set_buffer_id(GLuint id)
            {
                buffer_id_ = id;
            }

            GLenum type() const
            {
                return type_;
            }

            GLint size() const
            {
                return size_;
            }

            GLsizei stride() const
            {
                return gl_stride_;
            }

            GLboolean normalized() const
            {
                return normalized_;
            }

            const GLvoid* pointer() const
            {
                return pointer_;
            }

            bool IsClientSide() const
            {
                return buffer_id_ == 0;
            }

            GLuint divisor() const
            {
                return divisor_;
            }

            GLboolean integer() const
            {
                return integer_;
            }

            void SetInfo(
                GLuint buffer_id,
                GLint size,
                GLenum type,
                GLboolean normalized,
                GLsizei gl_stride,
                const GLvoid* pointer,
                GLboolean integer)
            {
                buffer_id_ = buffer_id;
                size_ = size;
                type_ = type;
                normalized_ = normalized;
                gl_stride_ = gl_stride;
                pointer_ = pointer;
                integer_ = integer;
            }

            void SetDivisor(GLuint divisor)
            {
                divisor_ = divisor;
            }

        private:
            // Whether or not this attribute is enabled.
            bool enabled_;

            // The id of the buffer. 0 = client side buffer.
            GLuint buffer_id_;

            // Number of components (1, 2, 3, 4).
            GLint size_;

            // GL_BYTE, GL_FLOAT, etc. See glVertexAttribPointer.
            GLenum type_;

            // GL_TRUE or GL_FALSE
            GLboolean normalized_;

            // The pointer/offset into the buffer.
            const GLvoid* pointer_;

            // The stride that will be used to access the buffer. This is the bogus GL
            // stride where 0 = compute the stride based on size and type.
            GLsizei gl_stride_;

            // Divisor, for geometry instancing.
            GLuint divisor_;

            GLboolean integer_;
        };

        typedef std::vector<VertexAttrib> VertexAttribs;

        explicit VertexArrayObject(GLuint max_vertex_attribs);

        void UnbindBuffer(GLuint id);

        bool BindElementArray(GLuint id);

        bool HaveEnabledClientSideBuffers() const;

        void SetAttribEnable(GLuint index, bool enabled);

        void SetAttribPointer(
            GLuint buffer_id,
            GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,
            const void* ptr, GLboolean integer);

        bool GetVertexAttrib(GLuint index, GLenum pname, uint32_t* param) const;

        void SetAttribDivisor(GLuint index, GLuint divisor);

        bool GetAttribPointer(GLuint index, GLenum pname, void** ptr) const;

        const VertexAttribs& vertex_attribs() const
        {
            return vertex_attribs_;
        }

        GLuint bound_element_array_buffer() const
        {
            return bound_element_array_buffer_id_;
        }

    private:
        const VertexAttrib* GetAttrib(GLuint index) const;

        int num_client_side_pointers_enabled_;

        // The currently bound element array buffer.
        GLuint bound_element_array_buffer_id_;

        VertexAttribs vertex_attribs_;

        DISALLOW_COPY_AND_ASSIGN(VertexArrayObject);
    };

    VertexArrayObject::VertexArrayObject(GLuint max_vertex_attribs)
        : num_client_side_pointers_enabled_(0)
        , bound_element_array_buffer_id_(0)
    {
        vertex_attribs_.resize(max_vertex_attribs);
    }

    void VertexArrayObject::UnbindBuffer(GLuint id)
    {
        if (id == 0) {
            return;
        }
        for (size_t ii = 0; ii < vertex_attribs_.size(); ++ii) {
            VertexAttrib& attrib = vertex_attribs_[ii];
            if (attrib.buffer_id() == id) {
                attrib.set_buffer_id(0);
                if (attrib.enabled()) {
                    ++num_client_side_pointers_enabled_;
                }
            }
        }
        if (bound_element_array_buffer_id_ == id) {
            bound_element_array_buffer_id_ = 0;
        }
    }

    bool VertexArrayObject::BindElementArray(GLuint id)
    {
        if (id == bound_element_array_buffer_id_) {
            return false;
        }
        bound_element_array_buffer_id_ = id;
        return true;
    }
    bool VertexArrayObject::HaveEnabledClientSideBuffers() const
    {
        return num_client_side_pointers_enabled_ > 0;
    }

    void VertexArrayObject::SetAttribEnable(GLuint index, bool enabled)
    {
        if (index < vertex_attribs_.size()) {
            VertexAttrib& attrib = vertex_attribs_[index];
            if (attrib.enabled() != enabled) {
                if (attrib.IsClientSide()) {
                    num_client_side_pointers_enabled_ += enabled ? 1 : -1;
                    DCHECK_GE(num_client_side_pointers_enabled_, 0);
                }
                attrib.set_enabled(enabled);
            }
        }
    }

    void VertexArrayObject::SetAttribPointer(
        GLuint buffer_id,
        GLuint index,
        GLint size,
        GLenum type,
        GLboolean normalized,
        GLsizei stride,
        const void* ptr,
        GLboolean integer)
    {
        if (index < vertex_attribs_.size()) {
            VertexAttrib& attrib = vertex_attribs_[index];
            if (attrib.IsClientSide() && attrib.enabled()) {
                --num_client_side_pointers_enabled_;
                DCHECK_GE(num_client_side_pointers_enabled_, 0);
            }

            attrib.SetInfo(buffer_id, size, type, normalized, stride, ptr, integer);

            if (attrib.IsClientSide() && attrib.enabled()) {
                ++num_client_side_pointers_enabled_;
            }
        }
    }

    bool VertexArrayObject::GetVertexAttrib(GLuint index,
        GLenum pname,
        uint32_t* param) const
    {
        const VertexAttrib* attrib = GetAttrib(index);
        if (!attrib) {
            return false;
        }

        switch (pname) {
        case GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:
            *param = attrib->buffer_id();
            break;
        case GL_VERTEX_ATTRIB_ARRAY_ENABLED:
            *param = attrib->enabled();
            break;
        case GL_VERTEX_ATTRIB_ARRAY_SIZE:
            *param = attrib->size();
            break;
        case GL_VERTEX_ATTRIB_ARRAY_STRIDE:
            *param = attrib->stride();
            break;
        case GL_VERTEX_ATTRIB_ARRAY_TYPE:
            *param = attrib->type();
            break;
        case GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
            *param = attrib->normalized();
            break;
        case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
            *param = attrib->integer();
            break;
        default:
            return false; // pass through to service side.
        }
        return true;
    }

    void VertexArrayObject::SetAttribDivisor(GLuint index, GLuint divisor)
    {
        if (index < vertex_attribs_.size()) {
            VertexAttrib& attrib = vertex_attribs_[index];
            attrib.SetDivisor(divisor);
        }
    }

    // Gets the Attrib pointer for an attrib but only if it's a client side
    // pointer. Returns true if it got the pointer.
    bool VertexArrayObject::GetAttribPointer(
        GLuint index, GLenum pname, void** ptr) const
    {
        const VertexAttrib* attrib = GetAttrib(index);
        if (attrib && pname == GL_VERTEX_ATTRIB_ARRAY_POINTER) {
            *ptr = const_cast<void*>(attrib->pointer());
            return true;
        }
        return false;
    }

    // Gets an attrib if it's in range and it's client side.
    const VertexArrayObject::VertexAttrib* VertexArrayObject::GetAttrib(
        GLuint index) const
    {
        if (index < vertex_attribs_.size()) {
            const VertexAttrib* attrib = &vertex_attribs_[index];
            return attrib;
        }
        return NULL;
    }

    VertexArrayObjectManager::VertexArrayObjectManager(
        GLuint max_vertex_attribs,
        GLuint array_buffer_id,
        GLuint element_array_buffer_id,
        bool support_client_side_arrays)
        : max_vertex_attribs_(max_vertex_attribs)
        , array_buffer_id_(array_buffer_id)
        , array_buffer_size_(0)
        , array_buffer_offset_(0)
        , element_array_buffer_id_(element_array_buffer_id)
        , element_array_buffer_size_(0)
        , collection_buffer_size_(0)
        , default_vertex_array_object_(new VertexArrayObject(max_vertex_attribs))
        , bound_vertex_array_object_(default_vertex_array_object_)
        , support_client_side_arrays_(support_client_side_arrays)
    {
    }

    VertexArrayObjectManager::~VertexArrayObjectManager()
    {
        for (VertexArrayObjectMap::iterator it = vertex_array_objects_.begin();
             it != vertex_array_objects_.end(); ++it) {
            delete it->second;
        }
        delete default_vertex_array_object_;
    }

    bool VertexArrayObjectManager::IsReservedId(GLuint id) const
    {
        return (id != 0 && (id == array_buffer_id_ || id == element_array_buffer_id_));
    }

    GLuint VertexArrayObjectManager::bound_element_array_buffer() const
    {
        return bound_vertex_array_object_->bound_element_array_buffer();
    }

    void VertexArrayObjectManager::UnbindBuffer(GLuint id)
    {
        bound_vertex_array_object_->UnbindBuffer(id);
    }

    bool VertexArrayObjectManager::BindElementArray(GLuint id)
    {
        return bound_vertex_array_object_->BindElementArray(id);
    }

    void VertexArrayObjectManager::GenVertexArrays(
        GLsizei n, const GLuint* arrays)
    {
        DCHECK_GE(n, 0);
        for (GLsizei i = 0; i < n; ++i) {
            std::pair<VertexArrayObjectMap::iterator, bool> result = vertex_array_objects_.insert(std::make_pair(
                arrays[i], new VertexArrayObject(max_vertex_attribs_)));
            DCHECK(result.second);
        }
    }

    void VertexArrayObjectManager::DeleteVertexArrays(
        GLsizei n, const GLuint* arrays)
    {
        DCHECK_GE(n, 0);
        for (GLsizei i = 0; i < n; ++i) {
            GLuint id = arrays[i];
            if (id) {
                VertexArrayObjectMap::iterator it = vertex_array_objects_.find(id);
                if (it != vertex_array_objects_.end()) {
                    if (bound_vertex_array_object_ == it->second) {
                        bound_vertex_array_object_ = default_vertex_array_object_;
                    }
                    delete it->second;
                    vertex_array_objects_.erase(it);
                }
            }
        }
    }

    bool VertexArrayObjectManager::BindVertexArray(GLuint array, bool* changed)
    {
        *changed = false;
        VertexArrayObject* vertex_array_object = default_vertex_array_object_;
        if (array != 0) {
            VertexArrayObjectMap::iterator it = vertex_array_objects_.find(array);
            if (it == vertex_array_objects_.end()) {
                return false;
            }
            vertex_array_object = it->second;
        }
        *changed = vertex_array_object != bound_vertex_array_object_;
        bound_vertex_array_object_ = vertex_array_object;
        return true;
    }

    bool VertexArrayObjectManager::HaveEnabledClientSideBuffers() const
    {
        return bound_vertex_array_object_->HaveEnabledClientSideBuffers();
    }

    void VertexArrayObjectManager::SetAttribEnable(GLuint index, bool enabled)
    {
        bound_vertex_array_object_->SetAttribEnable(index, enabled);
    }

    bool VertexArrayObjectManager::GetVertexAttrib(GLuint index,
        GLenum pname,
        uint32_t* param)
    {
        return bound_vertex_array_object_->GetVertexAttrib(index, pname, param);
    }

    bool VertexArrayObjectManager::GetAttribPointer(
        GLuint index, GLenum pname, void** ptr) const
    {
        return bound_vertex_array_object_->GetAttribPointer(index, pname, ptr);
    }

    bool VertexArrayObjectManager::SetAttribPointer(
        GLuint buffer_id,
        GLuint index,
        GLint size,
        GLenum type,
        GLboolean normalized,
        GLsizei stride,
        const void* ptr,
        GLboolean integer)
    {
        // Client side arrays are not allowed in vaos.
        if (buffer_id == 0 && !IsDefaultVAOBound()) {
            return false;
        }
        bound_vertex_array_object_->SetAttribPointer(
            buffer_id, index, size, type, normalized, stride, ptr, integer);
        return true;
    }

    void VertexArrayObjectManager::SetAttribDivisor(GLuint index, GLuint divisor)
    {
        bound_vertex_array_object_->SetAttribDivisor(index, divisor);
    }

    // Collects the data into the collection buffer and returns the number of
    // bytes collected.
    GLsizei VertexArrayObjectManager::CollectData(
        const void* data,
        GLsizei bytes_per_element,
        GLsizei real_stride,
        GLsizei num_elements)
    {
        GLsizei bytes_needed = bytes_per_element * num_elements;
        if (collection_buffer_size_ < bytes_needed) {
            collection_buffer_.reset(new int8_t[bytes_needed]);
            collection_buffer_size_ = bytes_needed;
        }
        const int8_t* src = static_cast<const int8_t*>(data);
        int8_t* dst = collection_buffer_.get();
        int8_t* end = dst + bytes_per_element * num_elements;
        for (; dst < end; src += real_stride, dst += bytes_per_element) {
            memcpy(dst, src, bytes_per_element);
        }
        return bytes_needed;
    }

    bool VertexArrayObjectManager::IsDefaultVAOBound() const
    {
        return bound_vertex_array_object_ == default_vertex_array_object_;
    }

    // Returns true if buffers were setup.
    bool VertexArrayObjectManager::SetupSimulatedClientSideBuffers(
        const char* function_name,
        GLES2Implementation* gl,
        GLES2CmdHelper* gl_helper,
        GLsizei num_elements,
        GLsizei primcount,
        bool* simulated)
    {
        *simulated = false;
        if (!support_client_side_arrays_)
            return true;
        if (!bound_vertex_array_object_->HaveEnabledClientSideBuffers()) {
            return true;
        }
        if (!IsDefaultVAOBound()) {
            gl->SetGLError(
                GL_INVALID_OPERATION, function_name,
                "client side arrays not allowed with vertex array object");
            return false;
        }
        *simulated = true;
        GLsizei total_size = 0;
        // Compute the size of the buffer we need.
        const VertexArrayObject::VertexAttribs& vertex_attribs = bound_vertex_array_object_->vertex_attribs();
        for (GLuint ii = 0; ii < vertex_attribs.size(); ++ii) {
            const VertexArrayObject::VertexAttrib& attrib = vertex_attribs[ii];
            if (attrib.IsClientSide() && attrib.enabled()) {
                size_t bytes_per_element = GLES2Util::GetGroupSizeForBufferType(attrib.size(), attrib.type());
                GLsizei elements = (primcount && attrib.divisor() > 0) ? ((primcount - 1) / attrib.divisor() + 1) : num_elements;
                total_size += RoundUpToMultipleOf4(bytes_per_element * elements);
            }
        }
        gl_helper->BindBuffer(GL_ARRAY_BUFFER, array_buffer_id_);
        array_buffer_offset_ = 0;
        if (total_size > array_buffer_size_) {
            gl->BufferDataHelper(GL_ARRAY_BUFFER, total_size, NULL, GL_DYNAMIC_DRAW);
            array_buffer_size_ = total_size;
        }
        for (GLuint ii = 0; ii < vertex_attribs.size(); ++ii) {
            const VertexArrayObject::VertexAttrib& attrib = vertex_attribs[ii];
            if (attrib.IsClientSide() && attrib.enabled()) {
                size_t bytes_per_element = GLES2Util::GetGroupSizeForBufferType(attrib.size(), attrib.type());
                GLsizei real_stride = attrib.stride() ? attrib.stride() : static_cast<GLsizei>(bytes_per_element);
                GLsizei elements = (primcount && attrib.divisor() > 0) ? ((primcount - 1) / attrib.divisor() + 1) : num_elements;
                GLsizei bytes_collected = CollectData(
                    attrib.pointer(), bytes_per_element, real_stride, elements);
                gl->BufferSubDataHelper(
                    GL_ARRAY_BUFFER, array_buffer_offset_, bytes_collected,
                    collection_buffer_.get());
                gl_helper->VertexAttribPointer(
                    ii, attrib.size(), attrib.type(), attrib.normalized(), 0,
                    array_buffer_offset_);
                array_buffer_offset_ += RoundUpToMultipleOf4(bytes_collected);
                DCHECK_LE(array_buffer_offset_, array_buffer_size_);
            }
        }
        return true;
    }

    // Copies in indices to the service and returns the highest index accessed + 1
    bool VertexArrayObjectManager::SetupSimulatedIndexAndClientSideBuffers(
        const char* function_name,
        GLES2Implementation* gl,
        GLES2CmdHelper* gl_helper,
        GLsizei count,
        GLenum type,
        GLsizei primcount,
        const void* indices,
        GLuint* offset,
        bool* simulated)
    {
        *simulated = false;
        *offset = ToGLuint(indices);
        if (!support_client_side_arrays_)
            return true;
        GLsizei num_elements = 0;
        if (bound_vertex_array_object_->bound_element_array_buffer() == 0) {
            *simulated = true;
            *offset = 0;
            GLsizei max_index = -1;
            switch (type) {
            case GL_UNSIGNED_BYTE: {
                const uint8_t* src = static_cast<const uint8_t*>(indices);
                for (GLsizei ii = 0; ii < count; ++ii) {
                    if (src[ii] > max_index) {
                        max_index = src[ii];
                    }
                }
                break;
            }
            case GL_UNSIGNED_SHORT: {
                const uint16_t* src = static_cast<const uint16_t*>(indices);
                for (GLsizei ii = 0; ii < count; ++ii) {
                    if (src[ii] > max_index) {
                        max_index = src[ii];
                    }
                }
                break;
            }
            case GL_UNSIGNED_INT: {
                uint32_t max_glsizei = static_cast<uint32_t>(std::numeric_limits<GLsizei>::max());
                const uint32_t* src = static_cast<const uint32_t*>(indices);
                for (GLsizei ii = 0; ii < count; ++ii) {
                    // Other parts of the API use GLsizei (signed) to store limits.
                    // As such, if we encounter a index that cannot be represented with
                    // an unsigned int we need to flag it as an error here.
                    if (src[ii] > max_glsizei) {
                        gl->SetGLError(
                            GL_INVALID_OPERATION, function_name, "index too large.");
                        return false;
                    }
                    GLsizei signed_index = static_cast<GLsizei>(src[ii]);
                    if (signed_index > max_index) {
                        max_index = signed_index;
                    }
                }
                break;
            }
            default:
                break;
            }
            gl_helper->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_array_buffer_id_);
            GLsizei bytes_per_element = GLES2Util::GetGLTypeSizeForBuffers(type);
            GLsizei bytes_needed = bytes_per_element * count;
            if (bytes_needed > element_array_buffer_size_) {
                element_array_buffer_size_ = bytes_needed;
                gl->BufferDataHelper(
                    GL_ELEMENT_ARRAY_BUFFER, bytes_needed, NULL, GL_DYNAMIC_DRAW);
            }
            gl->BufferSubDataHelper(
                GL_ELEMENT_ARRAY_BUFFER, 0, bytes_needed, indices);

            num_elements = max_index + 1;
        } else if (bound_vertex_array_object_->HaveEnabledClientSideBuffers()) {
            // Index buffer is GL buffer. Ask the service for the highest vertex
            // that will be accessed. Note: It doesn't matter if another context
            // changes the contents of any of the buffers. The service will still
            // validate the indices. We just need to know how much to copy across.
            num_elements = gl->GetMaxValueInBufferCHROMIUMHelper(
                               bound_vertex_array_object_->bound_element_array_buffer(),
                               count, type, ToGLuint(indices))
                + 1;
        }

        bool simulated_client_side_buffers = false;
        SetupSimulatedClientSideBuffers(
            function_name, gl, gl_helper, num_elements, primcount,
            &simulated_client_side_buffers);
        *simulated = *simulated || simulated_client_side_buffers;
        return true;
    }

} // namespace gles2
} // namespace gpu
